package liuyang.bigeventserver.modules.security.config;

import liuyang.bigeventserver.modules.security.filter.JsonLoginAuthenticationFilter;
import liuyang.bigeventserver.modules.security.filter.JwtOncePerRequestFilter;
import liuyang.bigeventserver.modules.security.handler.JwtLogoutSuccessHandler;
import liuyang.bigeventserver.modules.security.handler.RespJsonAuthenticationFailureHandler;
import liuyang.bigeventserver.modules.security.handler.JwtAuthenticationSuccessHandler;
import liuyang.bigeventserver.modules.security.handler.SessionRespJsonLogoutSuccessHandler;
import liuyang.bigeventserver.modules.security.handler.exception.RespJsonAccessDeniedHandler;
import liuyang.bigeventserver.modules.security.handler.exception.RespJsonAuthenticationEntryPoint;
import liuyang.bigeventserver.modules.security.service.DBUserDetailsServie;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 参考test-spring-boot-env中的版本。
 * 目标：演进为纯支持JWT的版本
 *
 * 文档：自定义配置
 * https://docs.spring.io/spring-security/reference/servlet/configuration/java.html
 *
 * 视频：
 * 雷丰阳
 * https://www.bilibili.com/video/BV1Es4y1q7Bf/?p=81&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 * 环环
 * https://www.bilibili.com/video/BV14b4y1A7Wz/?p=10&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 *
 * 断点提示：
 * 如果想观察filters, 可关注DefaultSecurityFilterChain
 *
 * 用户认证信息:
 * https://www.bilibili.com/video/BV14b4y1A7Wz/?p=29&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 * SecurityContextHolder
 *  SecurityContext
 *      Authentication
 *          - Principal
 *          - Credentials
 *          - Authorities
 *
 * @author xconf
 * @since 2024/2/26
 */
@Configuration
@EnableWebSecurity // 202402261500 实测 貌似并不是必须的（因为是在Spring Boot项目中）。应该是通过spi机制已经生效。
@AllArgsConstructor
public class SecurityConfig {

    // 认证流程行为处理
    private final JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;// jwt方案 签发jwt
    private final RespJsonAuthenticationFailureHandler respJsonAuthenticationFailureHandler;
    private final SessionRespJsonLogoutSuccessHandler sessionRespJsonLogoutSuccessHandler;// session方案
    private final JwtLogoutSuccessHandler jwtLogoutSuccessHandler;// jwt方案 销毁缓存信息
    private final JwtOncePerRequestFilter jwtOncePerRequestFilter;// jwt方案 验证jwt并从缓存中取出信息
    // 异常处理
    private final RespJsonAuthenticationEntryPoint respJsonAuthenticationEntryPoint;
    private final RespJsonAccessDeniedHandler respJsonAccessDeniedHandler;

    private static final String LOGIN_FILTER_PROCESS_URL = "/login";

    @Bean
    public PasswordEncoder passwordEncoder() {
        //return new BCryptPasswordEncoder();
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    // 参考：https://blog.csdn.net/lazy_LYF/article/details/127284459
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http
            , DBUserDetailsServie dbUserDetailsServie, PasswordEncoder passwordEncoder) throws Exception {
        final AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
        builder.userDetailsService(dbUserDetailsServie).passwordEncoder(passwordEncoder);
        return builder.build();
    }

    // 无登录页面版本的纯JSON交互登录组件
    @Bean
    public JsonLoginAuthenticationFilter jsonLoginAuthenticationFilter(AuthenticationManager authenticationManager) {
        JsonLoginAuthenticationFilter filter = new JsonLoginAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        filter.setAuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(respJsonAuthenticationFailureHandler);
        //filter.setPostOnly(false);
        filter.setFilterProcessesUrl(LOGIN_FILTER_PROCESS_URL);
        return  filter;
    }

    // https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#jc-httpsecurity
    // https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#_multiple_httpsecurity_instances
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, JsonLoginAuthenticationFilter jsonLoginAuthenticationFilter) throws Exception {
        // 认证保护
        http.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> {
            // 白名单
            authorizeHttpRequestsCustomizer
                    // test begin
                    .requestMatchers("/actuator/**").permitAll()
                    .requestMatchers("/articledemo/**").permitAll() // 测试用
                    .requestMatchers("/jsr303/**").permitAll()// 测试功能行为
                    //.requestMatchers("/user/**").permitAll()// TEMP 20240308 DEBUG
                    //.requestMatchers("/category/**").permitAll()
                    // test end
                    .requestMatchers("/user/regisger").permitAll()// login是/login，在过滤器中实现的。
                    .requestMatchers("/login").permitAll();// JsonLoginAuthenticationFilter
                    //.requestMatchers("/user/list").permitAll()// 为开发增加用户接口临时放开。
                    //.requestMatchers("/user/add").permitAll();// 为开发增加用户接口临时放开。

            // 其他页面需要认证
            authorizeHttpRequestsCustomizer
                    // 对多有请求开启授权保护
                    .anyRequest()
                    // 已认证的请求会自动授权
                    .authenticated();
        });

        // 带有登录页面的版本
        // 登录配置
        //http.formLogin(Customizer.withDefaults());// 调试的时候很有用！
        /*http.formLogin(form -> {
           form.loginPage("/login").permitAll();// 自定义 https://www.bilibili.com/video/BV14b4y1A7Wz/?p=21&vd_source=8bd7b24b38e3e12c558d839b352b32f4
           form.successHandler(jwtAuthenticationSuccessHandler);// 20240227 ok
           form.failureHandler(respJsonAuthenticationFailureHandler);// 20240227 ok
        });*/

        // 无登录页面的版本
        http.addFilterAt(jsonLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(jwtOncePerRequestFilter, JsonLoginAuthenticationFilter.class);

        // 登出配置
        http.logout(logout -> {
           //logout.logoutUrl("/logout");// 默认就是/logout
           //logout.logoutSuccessHandler(sessionRespJsonLogoutSuccessHandler);// 20240227 ok
            logout.logoutSuccessHandler(jwtLogoutSuccessHandler);
        });


        // Exception （指定相关组件会覆盖框架默认行为。默认的loginForm会消失，自定义的不会。为搭建时方便调试，可以先加上自定义登录页。）
        // 默认访问受保护页面会
        http.exceptionHandling(exceptionHandlingConfigurer -> {
            // 认证异常 https://www.bilibili.com/video/BV14b4y1A7Wz/?p=27&vd_source=8bd7b24b38e3e12c558d839b352b32f4
            exceptionHandlingConfigurer.authenticationEntryPoint(respJsonAuthenticationEntryPoint);// e.g. 未携带token访问了受保护的资源页面
            // 授权异常 https://www.bilibili.com/video/BV14b4y1A7Wz/?p=33&vd_source=8bd7b24b38e3e12c558d839b352b32f4
            exceptionHandlingConfigurer.accessDeniedHandler(respJsonAccessDeniedHandler);// e.g. 访问了没有权限的页面
        });

        // Session
        // 调试提示：在调试好每次从缓存(Redis)中取回用户信息的流程之后再把session修改为无状态。
        http.sessionManagement(session -> {
            session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        });

        // CORS 前后端分离必须配置。
        // 协议、域名、端口号有一个不一样，那就
        // https://www.bilibili.com/video/BV14b4y1A7Wz/?p=28&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
        http.cors(Customizer.withDefaults()) ;

        // CSRF 防火墙
        // REST 测试，POST请求这个必须要关掉，否则403!
        // https://www.bilibili.com/video/BV14b4y1A7Wz?p=17&vd_source=8bd7b24b38e3e12c558d839b352b32f4
        // input hidden _csrf
        http.csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

}
